El conjunto de datos del titanic es ampliamente concido en la comunidad del ML. Es más, forma parte de los retos de iniciación en la plataforma kaggle.
Este conjunto de datos, es la representación de las personas que embarcaron en el titanic. En el, se recogen multitud de datos sobre cada persona, relativos a su edad, pais y clase en la que embarcaron, además de si sobrevivieron o no.
Este cojunto de datos tiene la posibilidad de explicar algunos datos de la catastrofe. Puede aclarar si hubo algún condicionante para la muerte o supervivencia de las personas más allá del puro azar.
Este cojunto de datos pretende elaborar un modelo predictivo y con el responder a la pregunta: ¿Qué tipo de personas tenían más probabilidades de sobrevivir?
Antes de integrar, vamos a describir las variables que caracterízan a estos datos:
PassengerId Id del pasajero
Name Nombre del pasajero en formato cadena.
Sex Factor con los niveles male/female.
Age Valor numérico con la edad.
Pclass Factor con la clase del ticket(1 = 1st, 2 = 2nd, 3 = 3rd)
Embarked Factor con el embarque de la persona(C = Cherbourg, Q = Queenstown, S = Southampton).
Cabin camarote
Fare Valor numérico con el precio del ticket (NA para miembros de la tripulación musicos y empleados de la compañia).
SibSp Número de conyuges a bordo.
Parch Número de padres, hijos a bordo.
Survived Factor con dos niveles indicando si sobrevivió o no(0 = No, 1 = Yes).
Escogemos las columnas que serán factores:
factors = c("Sex"="factor","Pclass"="factor","Cabin"="factor", "Embarked"="factor","SibSp"="factor","Parch"="factor","Survived"="factor");
Cargamos los datos:
titanicData <- read.csv('titanic.csv', stringsAsFactors = FALSE, colClasses = factors );
summary(titanicData);
## PassengerId Survived Pclass Name Sex
## Min. : 1.0 0:549 1:216 Length:891 female:314
## 1st Qu.:223.5 1:342 2:184 Class :character male :577
## Median :446.0 3:491 Mode :character
## Mean :446.0
## 3rd Qu.:668.5
## Max. :891.0
##
## Age SibSp Parch Ticket Fare
## Min. : 0.42 0:608 0:678 Length:891 Min. : 0.00
## 1st Qu.:20.12 1:209 1:118 Class :character 1st Qu.: 7.91
## Median :28.00 2: 28 2: 80 Mode :character Median : 14.45
## Mean :29.70 3: 16 3: 5 Mean : 32.20
## 3rd Qu.:38.00 4: 18 4: 4 3rd Qu.: 31.00
## Max. :80.00 5: 5 5: 5 Max. :512.33
## NA's :177 8: 7 6: 1
## Cabin Embarked
## :687 : 2
## B96 B98 : 4 C:168
## C23 C25 C27: 4 Q: 77
## G6 : 4 S:644
## C22 C26 : 3
## D : 3
## (Other) :186
Los datos de interés de ese cojunto de datos serán los que nos aporten algo de información sobre las personas pero a nivel de cojunto, es decir datos como nombre, identificador de pasajero o número de ticket no nos resultan de utilidad. Por lo que podemos crear un conjunto de datos solamente con los datos adecuados:
cols_remove <- c("PassengerId", "Name", "Ticket")
titanicData <- titanicData[, !(colnames(titanicData) %in% cols_remove)]
summary(titanicData);
## Survived Pclass Sex Age SibSp Parch Fare
## 0:549 1:216 female:314 Min. : 0.42 0:608 0:678 Min. : 0.00
## 1:342 2:184 male :577 1st Qu.:20.12 1:209 1:118 1st Qu.: 7.91
## 3:491 Median :28.00 2: 28 2: 80 Median : 14.45
## Mean :29.70 3: 16 3: 5 Mean : 32.20
## 3rd Qu.:38.00 4: 18 4: 4 3rd Qu.: 31.00
## Max. :80.00 5: 5 5: 5 Max. :512.33
## NA's :177 8: 7 6: 1
## Cabin Embarked
## :687 : 2
## B96 B98 : 4 C:168
## C23 C25 C27: 4 Q: 77
## G6 : 4 S:644
## C22 C26 : 3
## D : 3
## (Other) :186
Para comprobar si los datos contienen elementos vacíos se ejecuta la siguiente sentencia.
colSums(is.na(titanicData))
## Survived Pclass Sex Age SibSp Parch Fare Cabin
## 0 0 0 177 0 0 0 0
## Embarked
## 0
Se puede observar que la columna Age contiene 177 valores nulos. Existen diferentes políticas para el tratamiento de los valores nulos:
Eliminarlos: En Ocasiones, compensa eliminar estas filas, ya que pueden generar distorsiones a la hora de hacer cálculos con las columnas que contienen los valores nulos.
Reemplazo: Se podrían reemplazar los valores por la media, la mediana o la moda. Estas medidas se pueden intentar particular en función de otras columnas para que no siempre sean los mismos para todas las entradas nulas.
Asignación de una categoría: Si se discretizan los datos en, por ejemplo, rangos de edad, se puede particularizar todos los valores nulos en una categoría especial llamada “edad desconocida”.
Predicción de los valores nulos: Por último, se pueden inferir los valores mediante predicciones.
En este caso, se van a inferir los valores en función de otros parámentros. Para hacer esto, partimos de que es muy probable que la edad media de las personas que viajan en Pclass3 es diferente a la edad media de las personas que viajan en Pclass1. Además, esa edad será diferente en función de si estamos ante un hombre o una mujer. Por tanto, para inferir los valores de edad perdidos, se agrupa por Sex y Pclass, para posteriormente calcular las medianas de cada serie agrupada.
Primero, se calcula la media y la mediana de edad en función de la clase y el género del pasajero.
by_sex_class <- titanicData %>% group_by(Sex, Pclass) %>% summarise(mean = mean(Age, na.rm = TRUE), median = median(Age, na.rm = TRUE))
by_sex_class
## # A tibble: 6 x 4
## # Groups: Sex [2]
## Sex Pclass mean median
## <fct> <fct> <dbl> <dbl>
## 1 female 1 34.6 35
## 2 female 2 28.7 28
## 3 female 3 21.8 21.5
## 4 male 1 41.3 40
## 5 male 2 30.7 30
## 6 male 3 26.5 25
Se observa que la media y la mediana de edad varía en función del género y la clase en la que viajaban. Se procede a rellenar los valores nulos en la columna de edad por los valores de mediana en función de Sex y Pclass.
titanicData$Age[titanicData$Sex == "female" & titanicData$Pclass == "1" & is.na(titanicData$Age)] <- by_sex_class$median[by_sex_class$Sex == "female" & by_sex_class$Pclass == "1"]
titanicData$Age[titanicData$Sex == "female" & titanicData$Pclass == "2" & is.na(titanicData$Age)] <- by_sex_class$median[by_sex_class$Sex == "female" & by_sex_class$Pclass == "2"]
titanicData$Age[titanicData$Sex == "female" & titanicData$Pclass == "3" & is.na(titanicData$Age)] <- by_sex_class$median[by_sex_class$Sex == "female" & by_sex_class$Pclass == "3"]
titanicData$Age[titanicData$Sex == "male" & titanicData$Pclass == "1" & is.na(titanicData$Age)] <- by_sex_class$median[by_sex_class$Sex == "male" & by_sex_class$Pclass == "1"]
titanicData$Age[titanicData$Sex == "male" & titanicData$Pclass == "2" & is.na(titanicData$Age)] <- by_sex_class$median[by_sex_class$Sex == "male" & by_sex_class$Pclass == "2"]
titanicData$Age[titanicData$Sex == "male" & titanicData$Pclass == "3" & is.na(titanicData$Age)] <- by_sex_class$median[by_sex_class$Sex == "male" & by_sex_class$Pclass == "3"]
Se vuelve a comprobar si existen valores nulos.
colSums(is.na(titanicData))
## Survived Pclass Sex Age SibSp Parch Fare Cabin
## 0 0 0 0 0 0 0 0
## Embarked
## 0
Se han eliminado los valores nulos. Ahora, se comprueba que el summary no difiere mucho del original.
summary(titanicData)
## Survived Pclass Sex Age SibSp Parch Fare
## 0:549 1:216 female:314 Min. : 0.42 0:608 0:678 Min. : 0.00
## 1:342 2:184 male :577 1st Qu.:21.50 1:209 1:118 1st Qu.: 7.91
## 3:491 Median :26.00 2: 28 2: 80 Median : 14.45
## Mean :29.11 3: 16 3: 5 Mean : 32.20
## 3rd Qu.:36.00 4: 18 4: 4 3rd Qu.: 31.00
## Max. :80.00 5: 5 5: 5 Max. :512.33
## 8: 7 6: 1
## Cabin Embarked
## :687 : 2
## B96 B98 : 4 C:168
## C23 C25 C27: 4 Q: 77
## G6 : 4 S:644
## C22 C26 : 3
## D : 3
## (Other) :186
La media de edad ha bajado ligeramente, pero en general los datos se mantienen estables aún habiendo inferido 177 entradas.
En el summary se pueden observar diferentes columnas con datos anómalos o vacíos. Se procede a analizar uno a uno cada caso.
La columna SibSp contiene muchos valores a 0. En esta columna este valor es perfectamente normal, ya que indica el número de hermanos o esposas a bordo del barco para cada persona.
La columna Parch también contienen valores a 0, pero también cuadra, ya que este campo indica el núero de padres o hijos a bordo.
La columna Fare indica el precio que el pasajero pagó para estar en el barco. El mínimno de esta columna es 0, lo cual no tendría demasiado sentido. Se procede a mirar cuántas entradas tienen 0 en el precio.
filter(titanicData, Fare == 0)
## Survived Pclass Sex Age SibSp Parch Fare Cabin Embarked
## 1 0 3 male 36 0 0 0 S
## 2 0 1 male 40 0 0 0 B94 S
## 3 1 3 male 25 0 0 0 S
## 4 0 2 male 30 0 0 0 S
## 5 0 3 male 19 0 0 0 S
## 6 0 2 male 30 0 0 0 S
## 7 0 2 male 30 0 0 0 S
## 8 0 2 male 30 0 0 0 S
## 9 0 3 male 49 0 0 0 S
## 10 0 1 male 40 0 0 0 S
## 11 0 2 male 30 0 0 0 S
## 12 0 2 male 30 0 0 0 S
## 13 0 1 male 39 0 0 0 A36 S
## 14 0 1 male 40 0 0 0 B102 S
## 15 0 1 male 38 0 0 0 S
Es destacable que todas aquellas entradas que tienen Fare = 0 son de hombres. Dado que no han pagado nada, estas personas podrían ser tripulación del barco, por lo que se mantienen estas entradas.
La columna Cabin contiene muchísimos valores nulos, y no se puede inferir de ninguna manera. Tampoco parece que aporte demasiada información útil, por lo que se desecha.
titanicData <- subset(titanicData, select = -c(Cabin))
Por último, la columna Embarked contiene dos valores nulos que sí serían interesantes de completar. En este caso, para evitar tener 4 categorías y que se distorsionen un poco esos datos, se imputan estos dos valores con la moda, es decir, con el valor “S”.
titanicData$Embarked[titanicData$Embarked == ""] <- "S"
# Se elimina la clase sobrante.
titanicData$Embarked <- as.factor(as.character(titanicData$Embarked))
Se hace un último summary para comprobar que todo ha quedado correctamente.
summary(titanicData)
## Survived Pclass Sex Age SibSp Parch Fare
## 0:549 1:216 female:314 Min. : 0.42 0:608 0:678 Min. : 0.00
## 1:342 2:184 male :577 1st Qu.:21.50 1:209 1:118 1st Qu.: 7.91
## 3:491 Median :26.00 2: 28 2: 80 Median : 14.45
## Mean :29.11 3: 16 3: 5 Mean : 32.20
## 3rd Qu.:36.00 4: 18 4: 4 3rd Qu.: 31.00
## Max. :80.00 5: 5 5: 5 Max. :512.33
## 8: 7 6: 1
## Embarked
## C:168
## Q: 77
## S:646
##
##
##
##
La mejor manera de tratar los valores extremos es ir mostrando los diferentes valores de columnas numéricas en un diagrama de cajas y bigotes o boxplot. Para la realización de estos diagramas se ha utilizado la librería Plotly.
fig <- plot_ly(y = titanicData$Age, type = "box", name = "Edad")
fig
Aunque vemos unos cuantos outliers en el campo Edad, son perfectamente normales.
Veamos la columna Fare.
fig <- plot_ly(y = titanicData$Fare, type = "box", name = "Precio del Ticket")
fig
Se observan bastantes outliers, pero hay un precio que destaca más que los demás. Vamos a analizar esas filas.
filter(titanicData, Fare > 500)
## Survived Pclass Sex Age SibSp Parch Fare Embarked
## 1 1 1 female 35 0 0 512.3292 C
## 2 1 1 male 36 0 1 512.3292 C
## 3 1 1 male 35 0 0 512.3292 C
Las tres filas pertenecen a personas del mismo rango de edad que embarcaron desde el mismo puerto. Por la exactitud de los datos y su homogeneidad, parecen datos correctos, por lo que se mantienen en el dataset.
Adicionalmente Crearemos un dato en base a la edad, diviendola en segmentos para así poder realizar un análisis por categorías:
titanicData$AgeGroup <- cut(titanicData$Age, breaks = c(0,10,20,30,40,50,60,70,100), labels = c("0-9", "10-19", "20-29", "30-39","40-49","50-59","60-69","70-79"))
plot(titanicData$AgeGroup)
En esta sección dividiremos el conjunto de datos en subconjuntos para analizar y compararlos entre ellos.
El conjunto de datos lo analizaremos en fución a los siguientes grupos para determinar si guardan relación con la supervivencia:
Vamos a mostrar con gráficas cada uno de los grupos nombrados junto con la supervivencia.
ggplot(data=titanicData[1:nrow(titanicData),],aes(x=AgeGroup,fill=Survived))+geom_bar()
ggplot(data=titanicData[1:nrow(titanicData),],aes(x=Pclass,fill=Survived))+geom_bar()
ggplot(data=titanicData[1:nrow(titanicData),],aes(x=Sex,fill=Survived))+geom_bar()
Para comprobar la normalidad se va a utilizar la prueba de la normalidad de Anderson Darling, se comprueba que el p-valor sea superior a 0.05.
alpha = 0.05
col.names = colnames(titanicData)
for (i in 1:ncol(titanicData)) {
if (i == 1) cat("Variables que no siguen una distribución normal:\n")
if (is.integer(titanicData[,i]) | is.numeric(titanicData[,i])) {
p_val = ad.test(titanicData[,i])$p.value
if (p_val < alpha) {
cat(col.names[i])
# Format output
if (i < ncol(titanicData) - 1) cat(", ")
if (i %% 3 == 0) cat("\n")
}
}
}
## Variables que no siguen una distribución normal:
## Age, Fare,